/*
 * main.c
 *
 * Copyright (C) 2011 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @version core.c 1.0
 * @package libbspcore
 * @author Dr.NP <np@bsgroup.org>
 * @update 03/16/2011
 */

/**
 * Main processor of libbspcore
 *
 * === CHANGELOG ===
 * [03/16/2011] - Creation
 */

#define _GNU_SOURCE

#include "bsp.h"

#include "bsp_core.h"
#include "bsp_app.h"
#include "bsp_status.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <unistd.h>
#include <err.h>
#include <sys/syscall.h>
#include <sys/eventfd.h>

struct core_t *c = NULL;

#ifdef BSP_IN_SERVER
int core_init(char *conf_file)
{
    int res;
    
    char *cf = (conf_file) ? strdup(conf_file) : DEFAULT_CONF_FILE;
    
    c = (struct core_t *) malloc(sizeof(struct core_t));
    res = conf_init();
    conf_load(cf);

    conf_set("CORE_NWORKERS", DEFAULT_NWORKERS, CONF_LEVEL_INIT);

    res = log_init();

    // Determain default nfiles and nworkers
    int ncpus = sysconf(_SC_NPROCESSORS_CONF);
    debug_info("Cpu core(s) : %d\n", ncpus);

    // Max file descriptor
    int nfiles = set_rlimit();
    debug_info("FD set size : %d\n", nfiles);

    int nworkers = (conf_get("CORE_NWORKERS")) ? atoi(conf_get("CORE_NWORKERS")) : -1;
    if (nworkers <= 0)
    {
        nworkers = ncpus * 2;
    }
    
    c->nworkers = nworkers;

    // Set daemonize
    conf_set("CORE_DAEMONIZE", "0", CONF_LEVEL_INIT);
    if (0 != atoi(conf_get("CORE_DAEMONIZE")))
    {
        daemonize();
    }

    // Capture some signals
    set_sig();
    
    // Set fd list
    res = fdset_init(nfiles);
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "FDset init failed!\n");
    }

    // Init threads
    res = thread_init(nworkers);
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Worker threads init failed!\n");
    }

    // Init socket
    res = socket_init();
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Socket init failed!\n");
    }

    // Init script container
    res = script_init(nworkers);
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Script runner init failed!\n");
    }
    
    // Init memory pool
    res = mempool_init();
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Memory pool init failed!\n");
    }
    
    // Init object
    res = object_init();
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Object container init failed!\n");
    }

    // Init status
    //res = status_init();

    c->epoll_fd = epoll_create(EPOLL_WAIT_CONNS);
    set_fd(c->epoll_fd, -1, FD_TYPE_EPOLL);

    c->notify_event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    struct epoll_event ev;
    ev.data.fd = c->notify_event_fd;
    ev.events = EPOLLIN;
    set_fd(c->notify_event_fd, 0, FD_TYPE_EVENT);
    
    epoll_ctl(c->epoll_fd, EPOLL_CTL_ADD, c->notify_event_fd, &ev);
    
    return RTN_SUCCESS;
}

void core_loop(void (* func)(int))
{
    if (!c)
    {
        // You kidding me...
        return;
    }
    
    int nfds, i, what, client_id;
    struct epoll_event *events;
    struct fd_info_t *f = NULL;
    struct server_t *s = NULL;
    
    // Go go go!
    c->is_running = 1;
    c->start_time = time((time_t *) NULL);
    events = malloc(sizeof(struct epoll_event) * EPOLL_WAIT_CONNS);
    set_sig(func);
    
    // Waiting for all threads
    sleep(1);
    
    // Main loop
    while (1)
    {
        memset(events, 0, sizeof(struct epoll_event) * EPOLL_WAIT_CONNS);
        nfds = epoll_wait(c->epoll_fd, events, EPOLL_WAIT_CONNS, -1);
        
        for (i = 0; i < nfds; i ++)
        {
            what = events[i].events;
            
            if (!what & EPOLLIN || events[i].data.fd < 0)
            {
                continue;
            }
            
            f = get_fd(events[i].data.fd);
            
            if (!f)
            {
                continue;
            }
            
            if (f->type != FD_TYPE_SERVER)
            {
                continue;
            }
            
            s = get_server(f->id);
            if (!s)
            {
                continue;
            }
            
            do
            {
                client_id = accept_client(s);
                
                if (client_id < 0)
                {
                    break;
                }
            } while(1);
        }
    }
    
    return;
}

#else
int core_init(char *conf_file)
{
    int res;
    
    c = (struct core_t *) malloc(sizeof(struct core_t));
    
    // We do not need configuration file sir
    // Just 1 worker
    c->nworkers = 1;

    int nfiles = get_max_fds();
    
    // Set fd list
    res = fdset_init(nfiles);
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "FDset init failed!");
    }

    // Init threads
    res = thread_init(c->nworkers);
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Worker threads init failed!");
    }

    // Init socket
    res = socket_init();
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Socket init failed!");
    }
    
    // Init memory pool
    res = mempool_init();
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Memory pool init failed!\n");
    }
    
    // Init object
    res = object_init();
    if (res != RTN_SUCCESS)
    {
        err(RTN_ERROR_FATAL, "Object container init failed!\n");
    }

    c->epoll_fd = epoll_create(EPOLL_WAIT_CONNS);
    set_fd(c->epoll_fd, -1, FD_TYPE_EPOLL);
    
    return RTN_SUCCESS;
}

void core_loop(void (*func)(int))
{
    c->start_time = time((time_t *) NULL);
    set_sig(func);
    
    // If BSP_IN_CONSOLE, we do not need any loop here.
    
    return;
}

#endif  /* BSP_IN_SERVER */



int core_add_connector(int cnt_id)
{
    if (!c)
    {
        return RTN_ERROR_GENERAL;
    }

    dispatch_connector(cnt_id);
    
    return RTN_SUCCESS;
}

int core_add_timer(int timer_id)
{
    if (!c)
    {
        return RTN_ERROR_GENERAL;
    }
    
    dispatch_timer(timer_id);
    
    return RTN_SUCCESS;
}

int core_add_server(int srv_id)
{
    if (!c)
    {
        return RTN_ERROR_GENERAL;
    }

    struct server_t *s = get_server(srv_id);

    if (!s)
    {
        return RTN_ERROR_GENERAL;
    }

    s->start_time = time((time_t *) NULL);
    epoll_ctl(c->epoll_fd, EPOLL_CTL_ADD, s->fd, &s->ev);

    // Touch the loop
    write(c->notify_event_fd, "BSP", 3);

    return RTN_SUCCESS;
}
